"""
Paddle OCR 专用 PDF 高亮工具

核心设计理念：
1. 使用与 Paddle OCR 完全相同的 144 DPI 渲染 PDF
2. 直接使用 Paddle 返回的像素坐标，无需转换
3. 坐标系统完全对齐，零偏差

坐标说明：
- Paddle OCR 返回：144 DPI 渲染下的像素坐标 [x_px, y_px, x_px, y_px]
- 本高亮器使用：144 DPI 渲染 PDF，坐标直接使用像素值
- 完全一致：渲染方式和坐标系统与 Paddle 100% 对齐
"""

import io
from pathlib import Path
from typing import List, Tuple, Union, Optional

import fitz  # PyMuPDF
import cv2
import numpy as np
from PIL import Image
from loguru import logger

from src.utils.exceptions import PDFConversionError
from src.utils.minio.client import get_minio_client, MinIOClientError


class PaddlePDFHighlighter:
    """
    Paddle OCR 专用 PDF 高亮器
    
    核心特点：
    - 使用 144 DPI 渲染（与 Paddle 完全一致）
    - 直接使用像素坐标，无需转换
    - 坐标系统完全对齐，确保高亮精确
    """
    
    # Paddle OCR 使用的 DPI（关键参数）
    PADDLE_DPI = 144
    
    # 默认高亮样式
    DEFAULT_HIGHLIGHT_COLOR = (0, 255, 255)  # 黄色 (BGR)
    DEFAULT_BORDER_COLOR = (0, 0, 255)       # 红色 (BGR)
    DEFAULT_ALPHA = 0.3
    DEFAULT_BORDER_WIDTH = 2
    
    @classmethod
    def highlight_single_region(
        cls,
        pdf_path: Union[str, Path, bytes],
        page_num: int,
        bbox: List[float],  # Paddle 的像素坐标 [x_px, y_px, x_px, y_px]
        **kwargs
    ) -> str:
        """
        高亮单个区域的便捷方法
        
        Args:
            pdf_path: PDF 文件路径或字节内容
            page_num: 页码（从0开始）
            bbox: Paddle 返回的像素坐标 [x0_px, y0_px, x1_px, y1_px]
            **kwargs: 其他参数（highlight_color, border_color, alpha, border_width）
            
        Returns:
            str: MinIO 上的图片访问 URL
            
        Example:
            >>> # Paddle 返回的原生像素坐标
            >>> bbox = [100.5, 200.3, 350.8, 250.9]
            >>> url = PaddlePDFHighlighter.highlight_single_region("doc.pdf", 0, bbox)
        """
        return cls.highlight_pdf_regions(pdf_path, page_num, [bbox], **kwargs)
    
    @classmethod
    def highlight_pdf_regions(
        cls,
        pdf_path: Union[str, Path, bytes],
        page_num: int,
        bboxes: List[List[float]],  # Paddle 的像素坐标列表
        highlight_color: Tuple[int, int, int] = DEFAULT_HIGHLIGHT_COLOR,
        border_color: Tuple[int, int, int] = DEFAULT_BORDER_COLOR,
        alpha: float = DEFAULT_ALPHA,
        border_width: int = DEFAULT_BORDER_WIDTH,
        output_format: str = 'PNG'
    ) -> str:
        """
        使用 Paddle 坐标系统高亮 PDF 区域
        
        Args:
            pdf_path: PDF 文件路径或字节内容
            page_num: 页码（从0开始）
            bboxes: Paddle 返回的像素坐标列表 [[x0, y0, x1, y1], ...]
                   注意：这是 144 DPI 下的像素坐标，直接使用
            highlight_color: 高亮颜色 (B, G, R) 格式
            border_color: 边框颜色 (B, G, R) 格式
            alpha: 高亮透明度 (0.0-1.0)
            border_width: 边框宽度（像素）
            output_format: 输出格式 ('PNG' 或 'JPEG')
            
        Returns:
            str: MinIO 上的图片访问 URL
            
        Raises:
            PDFConversionError: PDF 处理或上传失败时抛出
        """
        try:
            # 1. 打开 PDF 文档
            doc = cls._open_pdf(pdf_path)
            
            if page_num < 0 or page_num >= len(doc):
                raise PDFConversionError(
                    f"页码 {page_num} 超出范围（PDF 共 {len(doc)} 页）"
                )
            
            page = doc.load_page(page_num)
            
            # 2. 使用 144 DPI 渲染（与 Paddle 完全一致）
            zoom = cls.PADDLE_DPI / 72.0  # 144/72 = 2.0
            mat = fitz.Matrix(zoom, zoom)
            pix = page.get_pixmap(matrix=mat, alpha=False)
            
            logger.info(
                f"Paddle 高亮 | 页面: {page_num} | "
                f"DPI: {cls.PADDLE_DPI} | "
                f"图像尺寸: {pix.width}x{pix.height} px | "
                f"区域数: {len(bboxes)}"
            )
            
            # 3. 转换为 OpenCV 格式
            img_data = pix.tobytes("ppm")
            img_pil = Image.open(io.BytesIO(img_data))
            img_cv = cv2.cvtColor(np.array(img_pil), cv2.COLOR_RGB2BGR)
            
            # 4. 绘制高亮（坐标直接使用，无需转换）
            highlighted_img = cls._draw_highlights(
                img_cv,
                bboxes,
                highlight_color,
                border_color,
                alpha,
                border_width
            )
            
            # 5. 上传到 MinIO
            url = cls._upload_to_minio(highlighted_img, output_format)
            
            doc.close()
            
            logger.info(f"Paddle 高亮成功 | URL: {url[:60]}...")
            return url
            
        except fitz.FileDataError as e:
            raise PDFConversionError(f"PDF 文件损坏或格式不正确: {str(e)}")
        except MinIOClientError as e:
            raise PDFConversionError(f"上传图片到 MinIO 失败: {str(e)}")
        except Exception as e:
            logger.error(f"Paddle 高亮处理失败: {e}", exc_info=True)
            raise PDFConversionError(f"高亮处理失败: {str(e)}")
    
    @classmethod
    def _open_pdf(cls, pdf_path: Union[str, Path, bytes]) -> fitz.Document:
        """
        打开 PDF 文档
        
        Args:
            pdf_path: PDF 文件路径或字节内容
            
        Returns:
            fitz.Document: PDF 文档对象
        """
        if isinstance(pdf_path, (str, Path)):
            return fitz.open(str(pdf_path))
        elif isinstance(pdf_path, bytes):
            return fitz.open(stream=pdf_path, filetype="pdf")
        else:
            raise PDFConversionError(f"不支持的 PDF 输入类型: {type(pdf_path)}")
    
    @classmethod
    def _draw_highlights(
        cls,
        img: np.ndarray,
        bboxes: List[List[float]],  # 像素坐标
        highlight_color: Tuple[int, int, int],
        border_color: Tuple[int, int, int],
        alpha: float,
        border_width: int
    ) -> np.ndarray:
        """
        在图像上绘制高亮区域
        
        Args:
            img: OpenCV 图像 (BGR 格式)
            bboxes: 像素坐标列表（直接使用，无需转换）
            highlight_color: 高亮颜色
            border_color: 边框颜色
            alpha: 透明度
            border_width: 边框宽度
            
        Returns:
            np.ndarray: 绘制后的图像
        """
        result = img.copy()
        overlay = img.copy()
        
        h, w = img.shape[:2]
        
        for idx, bbox in enumerate(bboxes):
            if len(bbox) != 4:
                logger.warning(f"跳过无效 bbox[{idx}]: {bbox}（长度应为 4）")
                continue
            
            # 直接使用像素坐标，转为整数
            x0_px = int(round(bbox[0]))
            y0_px = int(round(bbox[1]))
            x1_px = int(round(bbox[2]))
            y1_px = int(round(bbox[3]))
            
            # 确保坐标在图像范围内
            x0_px = max(0, min(x0_px, w))
            y0_px = max(0, min(y0_px, h))
            x1_px = max(0, min(x1_px, w))
            y1_px = max(0, min(y1_px, h))
            
            # 检查有效性
            if x1_px <= x0_px or y1_px <= y0_px:
                logger.warning(
                    f"跳过无效区域[{idx}]: "
                    f"[{x0_px}, {y0_px}, {x1_px}, {y1_px}]"
                )
                continue
            
            logger.debug(
                f"绘制高亮区域[{idx}] | "
                f"输入坐标: {bbox} | "
                f"像素坐标: [{x0_px}, {y0_px}, {x1_px}, {y1_px}] | "
                f"尺寸: {x1_px - x0_px}x{y1_px - y0_px} px"
            )
            
            # 绘制半透明高亮
            cv2.rectangle(
                overlay,
                (x0_px, y0_px),
                (x1_px, y1_px),
                highlight_color,
                -1  # 填充
            )
            
            # 绘制边框
            cv2.rectangle(
                result,
                (x0_px, y0_px),
                (x1_px, y1_px),
                border_color,
                border_width
            )
        
        # 混合图层，实现半透明效果
        cv2.addWeighted(overlay, alpha, result, 1 - alpha, 0, result)
        
        return result
    
    @classmethod
    def _upload_to_minio(
        cls,
        img: np.ndarray,
        output_format: str = 'PNG',
        expires_days: int = 7
    ) -> str:
        """
        将 OpenCV 图像上传到 MinIO 并返回 URL
        
        Args:
            img: OpenCV 图像 (BGR 格式)
            output_format: 输出格式 ('PNG' 或 'JPEG')
            expires_days: 过期天数，默认 7 天后自动删除
            
        Returns:
            str: MinIO 上的图片访问 URL
            
        Raises:
            MinIOClientError: 上传失败时抛出
        """
        # 转换为 PIL Image
        img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
        img_pil = Image.fromarray(img_rgb)
        
        # 编码为指定格式
        buffer = io.BytesIO()
        if output_format.upper() == 'JPEG':
            # JPEG 不支持透明度
            if img_pil.mode == 'RGBA':
                img_pil = img_pil.convert('RGB')
            img_pil.save(buffer, format='JPEG', quality=95)
            content_type = 'image/jpeg'
        else:
            img_pil.save(buffer, format='PNG')
            content_type = 'image/png'
        
        # 上传到 MinIO，设置过期时间
        buffer.seek(0)
        img_bytes = buffer.getvalue()
        
        minio_client = get_minio_client()
        url = minio_client.upload_bytes(
            data=img_bytes,
            content_type=content_type,
            expires_days=expires_days
        )
        
        return url


# 便捷函数
def highlight_paddle_pdf(
    pdf_path: Union[str, Path, bytes],
    page_num: int,
    regions: List[List[float]],
    **kwargs
) -> str:
    """
    便捷函数：使用 Paddle 坐标高亮 PDF
    
    Args:
        pdf_path: PDF 文件路径或字节内容
        page_num: 页码（从0开始）
        regions: Paddle 的像素坐标列表 [[x0, y0, x1, y1], ...]
        **kwargs: 其他参数
            - highlight_color: 高亮颜色，BGR 格式（默认黄色）
            - border_color: 边框颜色，BGR 格式（默认红色）
            - alpha: 透明度 0.0-1.0（默认 0.3）
            - border_width: 边框宽度（默认 2）
            - output_format: 输出格式 'PNG'/'JPEG'（默认 PNG）
    
    Returns:
        str: MinIO 上的图片访问 URL
    
    Example:
        >>> # Paddle 返回的原生像素坐标
        >>> bbox = [100.5, 200.3, 350.8, 250.9]
        >>> url = highlight_paddle_pdf("doc.pdf", 0, [bbox])
    """
    return PaddlePDFHighlighter.highlight_pdf_regions(
        pdf_path, page_num, regions, **kwargs
    )







